This mini series about “Offline Web Applications” shows you how to build a web application that your users can use while they are offline. I will give a session about this topic at Herbstcampus 2012.

I have decided to create a simple dictionary web application using wicket. This application allows you to enter a German word and its English translation. It lists all the translations it already knows.

Screenshot Dictionary App

The rest of this blog posting will show you the whole source code of this application. So, there will be mostly code. This can - frankly - be a little bit boring. But the other parts of this mini series will gradually change this code to make the application available offline, and I think this code is necessary to understand the future steps. So, here it is.

DictionaryPage.html

Let’s start with what you see when you start the application: The dictionary page. Notice that it uses a stylesheet which has to be made available for offline use later. Otherwise there is nothing too fancy about this page.

<html>
    <head>
        <wicket:link>
            <link rel="stylesheet" type="text/css" href="style.css"/>
        </wicket:link>
    </head>
    <body>
        <div class="content">
            <h1>German - English dictionary</h1>
            
            <div class="block grey">
                <h2>Add word</h2>
                
                <form wicket:id="addForm">
                    <div class="left">
                        <h3>German</h3>
                        <input type="text" wicket:id="german"/>
                    </div>
                    <div class="right">
                        <h3>English</h3>
                        <input type="text" wicket:id="english"/>
                    </div>
                    <div class="clear"/>
                    <input type="submit" value="Add"/>
                </form>
            </div>
            
            <div class="block green">
                <h2>Words</h2>
                
                <div class="left"><h3>German</h3></div>
                <div class="right"><h3>English</h3></div>
                <div class="clear"/>
                
                <div wicket:id="wordsList">
                    <div class="left" wicket:id="listEntryGerman"/>
                    <div class="right" wicket:id="listEntryEnglish"/>
                    <div class="clear"/>
                </div>
            </div>
        </div>
    </body>
</html>

DictionaryPage.java

There is a java class backing the HTML view above. All it does is wire up the components. It redirects everything domain-related to a service.

public class DictionaryPage extends WebPage {
    private static final long serialVersionUID = 1L;

    @Inject private DictionaryService dictService;
    
    private String germanWord;
    private String englishWord;
    
    public DictionaryPage() {
        Form<Void> addForm = new Form<Void>("addForm") {
            private static final long serialVersionUID = 1L;
            
            @Override
            protected void onSubmit() {
                dictService.addWord(germanWord, englishWord);
                germanWord = null;
                englishWord = null;
            }
        };
        add(addForm);
        
        TextField<String> german = new TextField<>("german", 
            new PropertyModel<String>(this, "germanWord"));
        addForm.add(german);
        
        TextField<String> english = new TextField<>("english", 
            new PropertyModel<String>(this, "englishWord"));
        addForm.add(english);
        
        DictionaryDataProvider dictionaryDataProvider = dictService.getDataProvider();
        DataView<DictionaryWordEntity> wordsList = new 
            DataView<DictionaryWordEntity>("wordsList", dictionaryDataProvider) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void populateItem(final Item<DictionaryWordEntity> item) {
                item.add(new Label("listEntryGerman", item.getModelObject().getGerman()));
                item.add(new Label("listEntryEnglish", 
                    item.getModelObject().getEnglish()));
            }
        };
        add(wordsList);
    }
}

Wicket Application and Guice Module

The wicket application and the guice module (which configures the injector and allows us to @Inject dependencies) comple the wicket related stuff. The file “web.xml”, which has to set up the wicket filter and the application, is not shown here.

ltpublic class DictionaryApplication extends WebApplication {
    private GuiceComponentInjector injector;

    @Override
    protected void init() {
        super.init();
        
        injector = new GuiceComponentInjector(this, new 
            DictionaryModule(getServletContext().getRealPath("dictionary.database")));
        getComponentInstantiationListeners().add(injector);
    }
    
    @Override
    public Class<? extends Page> getHomePage() {
        return DictionaryPage.class;
    }
}

public class DictionaryModule implements Module {
    private String databasePath;

    public DictionaryModule(String databasePath) {
        this.databasePath = databasePath;
    }

    @Override
    public void configure(Binder binder) {
        Map<String, String> properties = new HashMap<>();
        properties.put("hibernate.connection.url", "jdbc:hsqldb:file:"+databasePath);
        binder.bind(EntityManagerFactory.class).toInstance(
            Persistence.createEntityManagerFactory("dictionary", properties));
    }
}

Dictionary Service and Data Provider

The dictionary service contains the “business logic” of the dictionary. It is responsible for creating dictionary entries. It can also create a data provider, which is needed by the view to display the stored dictionary entries.

@Singleton
public class DictionaryService implements Serializable {
    private static final long serialVersionUID = 1L;

    @Inject private DictionaryRepository repository;
    @Inject private DictionaryWordEntityFactory entityFactory;
    
    public void addWord(final String german, final String austrian) {
        assert german != null && !german.isEmpty();
        assert austrian != null && !austrian.isEmpty();
        
        DictionaryWordEntity entity = entityFactory.createEntity(german, austrian);
        assert entity != null : "Entity can never be null at this point";
        
        repository.add(entity);
    }

    public DictionaryDataProvider getDataProvider() {
        return new DictionaryDataProvider(repository);
    }
}

public class DictionaryDataProvider implements IDataProvider<DictionaryWordEntity> {
    private static final long serialVersionUID = 1L;

    private final DictionaryRepository repository;
    
    public DictionaryDataProvider(final DictionaryRepository repository) {
        this.repository = repository;
    }

    @Override
    public void detach() {
    }

    @Override
    public Iterator<? extends DictionaryWordEntity> iterator(
            final int first, final int count) {
        return repository.select(first, count).iterator();
    }

    @Override
    public IModel<DictionaryWordEntity> model(final DictionaryWordEntity entity) {
        return new Model<DictionaryWordEntity>(entity);
    }

    @Override
    public int size() {
        return repository.size();
    }
}

DictionaryRepository and DictionaryWordEntity

The repository is responsible for managing a list of entities. Entities are read only after they are created. Entities can only be created using a factory.

@Entity
public class DictionaryWordEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String german;
    private String austrian;
    
    public Long getId() {
        return id;
    }
    public String getGerman() {
        return german;
    }
    public String getAustrian() {
        return austrian;
    }
    
    public static class DictionaryWordEntityFactory {
        public DictionaryWordEntity createEntity(
                final String german, final String austrian) {
            DictionaryWordEntity entity = new DictionaryWordEntity();
            
            entity.german = german;
            entity.austrian = austrian;
            
            return entity;
        }
    }
}

@Singleton
public class DictionaryRepository implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Inject private EntityManagerFactory emFactory;

    protected EntityManager newEm() {
        return emFactory.createEntityManager();
    }

    public void add(final DictionaryWordEntity entity) {
        assert entity != null;
        assert entity.getId() == null : "Add only works with new entities (where id==null)";
        
        EntityManager em = newEm();
        assert em != null : "Entity manager can never be null at this point";
        
        try {
            em.getTransaction().begin();
            em.persist(entity);
            em.getTransaction().commit();
            assert entity.getId() != null : "Entity always has an id at this point.";
        } finally {
            em.close();
        }
    }

    public int size() {
        EntityManager em = newEm();
        assert em != null : "Entity manager can never be null at this point";
        
        try {
            return ((Long) newEm().createQuery(
                "select count(dw.german) from DictionaryWordEntity dw")
                .getSingleResult()).intValue();
        } finally {
            em.close();
        }
    }

    public List<DictionaryWordEntity> select(final int first, final int count) {
        EntityManager em = newEm();
        assert em != null : "Entity manager can never be null at this point";
        
        try {
            return newEm().createQuery(
                "from DictionaryWordEntity dw order by dw.german asc")
                .setFirstResult(first).setMaxResults(count).getResultList();
        } finally {
            em.close();
        }
    }
}

The file “persistence.xml” which sets up an in-process HSQLDB is not shown here.

Coming up next

The next posting in this series will show how you can make all required assets available offline. This will enable us to see the page even if we have no connection to the server. To test it, we will simply shut the server down. So, stay tuned to see how we’ll make this application available offline.

Do you have any comments regarding this blog posting? Pleas contact me! You can find the contact info at the bottom of this page.